#!/usr/bin/env python3
import subprocess
import os
import sys
import argparse
import logging
import shutil
from pathlib import Path


EXIT_CONAN_NOT_FOUND = 11
EXIT_PROFILE_SET_ERROR = 12
EXIT_INVALID_TEST_PATH = 13
EXIT_RUN_TEST_FAILED = 14
EXIT_PYTEST_ERROR = 15
EXIT_MISSING_INSTALL = 16
EXIT_FAILING_INSTALL = 17

SUCCEEDED_BUILD = []
FAILED_BUILD = []

DRY_RUN = True
DRY_RUN_LOG = "[DRY]"

logger = logging.getLogger('BingoBuild')
logger.setLevel(logging.INFO)
formatter = logging.Formatter('[%(name)s] - [%(levelname)s] - %(message)s')
console_handler = logging.StreamHandler()
console_handler.setFormatter(formatter)
logger.addHandler(console_handler)


def _run_command(cmd: str, cwd: str = None) -> bool:
    if DRY_RUN:
        logger.info(f"{DRY_RUN_LOG} {cmd}")
        return True, None
    
    logger.info(f"Running: {cmd}")
    try:
        proc = subprocess.Popen(
            cmd,
            cwd=cwd,
            shell=True,
            stdout=subprocess.PIPE,
            stderr=subprocess.STDOUT,
            text=True,
            bufsize=1,
            universal_newlines=True,
            encoding="utf-8"
        )

        outputs = []
        while True:
            output = proc.stdout.readline().strip()
            if output == "" and proc.poll() is not None:
                break
            if output:
                logger.info(output)
                outputs.append(output)

        exit_code = proc.poll()
        if exit_code != 0:
            logger.info(f"Command failed with code {exit_code}")
            return False, None
        return True, "\n".join(outputs)
    except Exception as e:
        logger.exception("Command failed", extra_data={"error": str(e), "status": "error"})
        return False, None


def _prepare_pytest():
    try:
        import pytest_cov
        logger.info("pytest already installed!")
        return
    except ImportError:
        pass

    logger.info("Install pytest...")
    ret, _ = _run_command("pip3 install pytest-cov")
    if not ret:
        logger.error("Install pytest FAILED!")
        sys.exit(EXIT_PYTEST_ERROR)

    logger.info("Install pytest SUCCEEDED!")


def _configure_conan(manifest_prof_path: Path) -> None:
    if not manifest_prof_path.is_file():
        logger.error(f"Invalid manifest profile: {manifest_prof_path}!")
        sys.exit(EXIT_PROFILE_SET_ERROR)

    try:
        ret, conan_home = _run_command("conan config home")
        if not ret:
            logger.error("No conan found!")
            sys.exit(EXIT_CONAN_NOT_FOUND)

        profiles_dir = Path(conan_home.strip()) / "profiles"
        profiles_dir.mkdir(exist_ok=True)

        dst_prof = profiles_dir / manifest_prof_path.name
        logger.info(f"Copy: {manifest_prof_path} -> {dst_prof}")
        shutil.copy(manifest_prof_path, dst_prof)

    except Exception as e:
        logger.exception("Command failed", extra_data={"error": str(e), "status": "error"})
        sys.exit(EXIT_PROFILE_SET_ERROR)


def _run_test(test_type: str, test_path: Path) -> None:
    if not test_path.exists():
        logger.error(f"Invalid tests path: {test_path}!")
        sys.exit(EXIT_INVALID_TEST_PATH)
    
    logger.info(f"Running {test_type.upper()} tests in {test_path}")
    cmd = f"pytest {test_path} -v"
    if test_type == "ut":
        cmd = cmd + " --cov=."
    ret, _ = _run_command(cmd)
    if not ret:
        logger.error(f"{test_type.upper()} tests FAILED!")
        sys.exit(EXIT_RUN_TEST_FAILED)
    else:
        logger.info(f"{test_type.upper()} tests SUCCEEDED!")
    

def main(args: argparse.Namespace) -> None:
    _prepare_pytest()
    _configure_conan(args.manifest_prof_path.expanduser())
    _run_test(args.test_type, args.test_path)


if __name__ == "__main__":
    parser = argparse.ArgumentParser()
    parser.add_argument("-d", "--dry-run", action="store_true", help="Simulation mode")
    parser.add_argument("-t", "--test-type", choices=["it", "ut"], required=True)
    parser.add_argument("-p", "--test-path", type=Path, default=Path.cwd())
    parser.add_argument("-m", "--manifest-prof-path", type=Path)

    args = parser.parse_args()

    DRY_RUN = args.dry_run

    main(args)